import {Component, Input, OnInit} from '@angular/core';
import {UtilComponent} from '../../interface/util-component.interface';
import Map from 'ol/Map';
import TileLayer from 'ol/layer/Tile';
import BingMaps from 'ol/source/BingMaps';

const kernels = {
    none: [
        0, 0, 0,
        0, 1, 0,
        0, 0, 0
    ],
    sharpen: [
        0, -1, 0,
        -1, 5, -1,
        0, -1, 0
    ],
    sharpenless: [
        0, -1, 0,
        -1, 10, -1,
        0, -1, 0
    ],
    blur: [
        1, 1, 1,
        1, 1, 1,
        1, 1, 1
    ],
    shadow: [
        1, 2, 1,
        0, 1, 0,
        -1, -2, -1
    ],
    emboss: [
        -2, 1, 0,
        -1, 1, 1,
        0, 1, 2
    ],
    edge: [
        0, 1, 0,
        1, -4, 1,
        0, 1, 0
    ]
};

@Component({
    selector: 'kylin-image-filters',
    templateUrl: './image-filters.component.html',
    styleUrls: ['./image-filters.component.less']
})
export class ImageFiltersComponent implements OnInit, UtilComponent {
    hasContent = true;
    map: Map;

    @Input() set data(val) {
        Object.assign(this, val);
    };

    kernel = 'sharpen';
    bingKey = 'AmQGw-4WGzWsh5iEn6QOJfk4UKe1gdTK9IGdrzh_kgU2jTT6e9U7XLOM7ck5r17_';
    imagery: TileLayer;
    selectedKernel: any;

    constructor() {
    }

    ngOnInit() {
        this.turnOn();
    }

    /**
     * Update the kernel and re-render on change.
     */
    renderMap() {
        this.selectedKernel = this.normalize(kernels[this.kernel]);
        this.map.render();
    }

    turnOn() {
        this.imagery = new TileLayer({
            source: new BingMaps({key: this.bingKey, imagerySet: 'Aerial'})
        });
        this.map.addLayer(this.imagery);
        this.selectedKernel = this.normalize(kernels[this.kernel]);

        /**
         * Apply a filter on "postcompose" events.
         */
        this.imagery.on('postcompose', (event) => {
            this.convolve(event.context, this.selectedKernel);
        });
    }

    turnOff() {
        this.map.removeLayer(this.imagery);
    }

    cancel(options?: any): any {
        this.turnOff();
    }

    normalize = (kernel) => {
        const len = kernel.length;
        const normal = new Array(len);
        let i, sum = 0;
        for (i = 0; i < len; ++i) {
            sum += kernel[i];
        }
        if (sum <= 0) {
            (normal as any).normalized = false;
            sum = 1;
        } else {
            (normal as any).normalized = true;
        }
        for (i = 0; i < len; ++i) {
            normal[i] = kernel[i] / sum;
        }
        return normal;
    }
    /**
     * Apply a convolution kernel to canvas.  This works for any size kernel, but
     * performance starts degrading above 3 x 3.
     * @param {CanvasRenderingContext2D} context Canvas 2d context.
     * @param {Array<number>} kernel Kernel.
     */
    convolve = (context, kernel) => {
        const canvas = context.canvas;
        const width = canvas.width;
        const height = canvas.height;

        const size = Math.sqrt(kernel.length);
        const half = Math.floor(size / 2);

        const inputData = context.getImageData(0, 0, width, height).data;

        const output = context.createImageData(width, height);
        const outputData = output.data;

        for (let pixelY = 0; pixelY < height; ++pixelY) {
            const pixelsAbove = pixelY * width;
            for (let pixelX = 0; pixelX < width; ++pixelX) {
                let r = 0, g = 0, b = 0, a = 0;
                for (let kernelY = 0; kernelY < size; ++kernelY) {
                    for (let kernelX = 0; kernelX < size; ++kernelX) {
                        const weight = kernel[kernelY * size + kernelX];
                        const neighborY = Math.min(
                            height - 1, Math.max(0, pixelY + kernelY - half));
                        const neighborX = Math.min(
                            width - 1, Math.max(0, pixelX + kernelX - half));
                        const inputIndex = (neighborY * width + neighborX) * 4;
                        r += inputData[inputIndex] * weight;
                        g += inputData[inputIndex + 1] * weight;
                        b += inputData[inputIndex + 2] * weight;
                        a += inputData[inputIndex + 3] * weight;
                    }
                }
                const outputIndex = (pixelsAbove + pixelX) * 4;
                outputData[outputIndex] = r;
                outputData[outputIndex + 1] = g;
                outputData[outputIndex + 2] = b;
                outputData[outputIndex + 3] = kernel.normalized ? a : 255;
            }
        }
        context.putImageData(output, 0, 0);
    }
}

